﻿using System;
using System.Collections.Generic;
using System.Linq;
using CI.Organism;

namespace CI.Mesopotamia
{
    ///<summary>
    /// Evolving Fitness function template
    ///</summary>
    public abstract class EvolvingFitnessFunction : FitnessFunction
    {
        protected readonly Dictionary<IOrganism, int> OrganismsFitnessLevel = new Dictionary<IOrganism, int>();
        protected readonly List<IFitnessLevelEvaluator> FitnessLevelEvaluators = new List<IFitnessLevelEvaluator>();

        public int LowestAvailableFitnessLevel()
        {
            return 0;
        }

        public int HighestAvailableFitnessLevel()
        {
            return FitnessLevelEvaluators.Count - 1;
        }

        ///<summary>
        /// Gets the highelst level fitness function
        ///</summary>
        ///<returns></returns>
        protected int HightestUsedFitnessLevel()
        {
            return OrganismsFitnessLevel.Values.Max();
        }

        /// <summary>
        /// Update each organism with its fitness evaluation relative to its level
        /// </summary>
        /// <param name="organisms"></param>
        public override void UpdateOrganisms(IOrganismCollection organisms)
        {
            // check to see if each organism exists in the organisms fitness level collection
            // add remove to keep those lists correct
            lock (OrganismsFitnessLevel)
            {
                // build the dirty locations
                organisms.DirtyLocationsRebuild();

                // see if there are new organisms to add or old ones to remove
                UpdateNewAndRemovedOrganisms(organisms);

                // run the fitness level evaluators for each on
                foreach (var kvp in new Dictionary<IOrganism, int>(OrganismsFitnessLevel))
                {
                    // update its strength
                    FitnessLevelEvaluators[kvp.Value].UpdateStrength(kvp.Key);

                    // if its strong enough upgrade it
                    if (FitnessLevelEvaluators[kvp.Value].UpgradeToNextLevel(kvp.Key))
                    {
                        SimEnv.TraceLogger.LogWarn("Upgrading to next level : " + kvp.Key.Guid);
                        SimEnv.TraceLogger.LogWarn(FitnessLevelEvaluators[kvp.Value].ToString(kvp.Key));

                        // make sure their not at the current top level
                        if (kvp.Value == HighestAvailableFitnessLevel())
                            continue;

                        FitnessLevelEvaluators[OrganismsFitnessLevel[kvp.Key]].RemoveOrganism(kvp.Key);

                        OrganismsFitnessLevel[kvp.Key] = kvp.Value + 1;

                    }
                }
            }
            return;
        }

        protected virtual void UpdateNewAndRemovedOrganisms(IOrganismCollection organisms)
        {
            foreach (var newOrganisms in organisms)
            {
                if (!OrganismsFitnessLevel.ContainsKey(newOrganisms))
                    OrganismsFitnessLevel[newOrganisms] = 0;
            }

            var higestFitnessLevel = HightestUsedFitnessLevel();

            // see if their are any old organisms to remove
            foreach (var oldOrganism in OrganismsFitnessLevel.Keys.ToList())
            {
                if (!organisms.Alive.Contains(oldOrganism))
                {
                    if (OrganismsFitnessLevel[oldOrganism] == higestFitnessLevel && (LevelCount(higestFitnessLevel) == 1))
                        continue;

                    // remove it from the eff.
                    FitnessLevelEvaluators[OrganismsFitnessLevel[oldOrganism]].RemoveOrganism(oldOrganism);

                    // remove it from the list
                    OrganismsFitnessLevel.Remove(oldOrganism);
                }
            }
        }

        private int LevelCount(int highestLevelCount)
        {
            return OrganismsFitnessLevel.Where(x => x.Value == highestLevelCount).Count();
        }

        /// <summary>
        /// The fitness will be zero unless it is in the peak group and has children
        /// </summary>
        /// <param name="organism"></param>
        /// <returns></returns>
        public override long EvaluateFitnessAndUpdateInternalFitnessValue(IOrganism organism)
        {
            // if it has no children return we dont want it
            var isParent = IfParentAddFitness(organism);

            // reset fitness value
            organism.FitnessValue = 0;

            if (!isParent)
                return 0;

            if (!OrganismsFitnessLevel.ContainsKey(organism))
                return 0;

            if (OrganismsFitnessLevel[organism] != HightestUsedFitnessLevel())
                return 0;

            if (OrganismsFitnessLevel[organism] == LowestAvailableFitnessLevel())
                return 0;

            // add the organism energy - start energy
            organism.FitnessValue += organism.Energy - organism.InitEnergy;

            return organism.FitnessValue;
        }
    }
}
